#include <stdio.h>
#include <libusb.h>

#include "common.h"
#include "intf_usb.h"
#include <pthread.h>
#include "ofprint_common.h"


int32 intf_usb_init(void)
{
    int ret = -1;
    ret = libusb_init(&(dev_ofprint->ctx));
    return ret;
}

int32 intf_usb_free(void)
{
    libusb_exit(dev_ofprint->ctx);
    return 0;
}


static int intf_usb_get_endpoint(INTF_USB_ID_STRUCT *usb_id, uint8 *endpoint_in, uint8 *endpoint_out)
{
    int rv = -2;
    int i = 0;
    int r;
    int ret = -2;

    libusb_device **devs;
    libusb_device *dev;

    if((NULL == usb_id) || (NULL == endpoint_in) || (NULL == endpoint_out))
    {
        WD_ERR("the param is ERROR!\n");
        return -1;
    }

    pthread_mutex_lock(&(dev_ofprint->usb_mutex));

    rv = libusb_get_device_list(dev_ofprint->ctx, &devs);
    if (rv < 0)
    {
        goto LIBUSB_GET_DEVICE_LIST_FAILED;
    }

    i = 0;
    while ((dev = devs[i++]) != NULL)
    {
        struct libusb_device_descriptor desc;
        struct libusb_config_descriptor *usb_cfg_desc;

        rv = libusb_get_device_descriptor(dev, &desc);
        if(rv < 0) {
            continue;
        }

        if((desc.idProduct == usb_id->idProduct) && (desc.idVendor == usb_id->idVendor))
        {
            for(int j = 0; j < desc.bNumConfigurations; j++)
            {
                r = libusb_get_config_descriptor(dev, j, &usb_cfg_desc);

                if(0 != r)
                {
                    WD_ERR("libusb_get_config_descriptor %d: failed!!!\n", j);
                    continue;
                }

                for(uint8 l = 0; l < usb_cfg_desc->bNumInterfaces; l++)
                {
                    for(uint8 n = 0; n < usb_cfg_desc->interface[l].num_altsetting; n++)
                    {
                        for(uint8 m = 0; m < usb_cfg_desc->interface[l].altsetting[n].bNumEndpoints; m++)
                        {
                            uint8 endpointAddress = usb_cfg_desc->interface[l].altsetting[n].endpoint[m].bEndpointAddress;
                            if((0 < endpointAddress) && (0x80 & endpointAddress))
                            {
                                *endpoint_in = endpointAddress;
                                D_DBG("endpoint_in = 0x%02x\n", *endpoint_in);
                            }
                            else if((0 < endpointAddress) && !(0x80 & endpointAddress))
                            {
                                *endpoint_out = endpointAddress;
                                D_DBG("endpoint_out = 0x%02x\n", *endpoint_out);
                            }
                        }
                    }
                }
                ret = 0;
                libusb_free_config_descriptor(usb_cfg_desc);
            }

            goto LIBUSB_GET_DEVICE_LIST_SUCCESS;
        }
    }

LIBUSB_GET_DEVICE_LIST_SUCCESS:
    libusb_free_device_list(devs, 1);

LIBUSB_GET_DEVICE_LIST_FAILED:
    pthread_mutex_unlock(&(dev_ofprint->usb_mutex));

    return ret;
}


int32 intf_usb_open(INTF_USB_ID_STRUCT *usb_id, INTF_USB_HANDLE_STRUCT *usb_handle)
{
    libusb_device_handle *handle;
    int ret = -1;
    uint8 bInterfaceNumber = 0;

    if((NULL == usb_id) || (NULL == usb_handle))
    {
        WD_ERR("the param is ERROR!\n");
        return DRV_ERR;
    }

    if(NULL != usb_handle->handle)
    {
        WD_DBG("the usb device %04x:%04x not close.\n", usb_id->idVendor, usb_id->idProduct);
        return DRV_OK;
    }

    if(0 != intf_usb_get_endpoint(usb_id, &(usb_handle->endpoint_in), &(usb_handle->endpoint_out)))
    {
        //WD_ERR("intf_usb_get_endpoint %04x:%04x : FAIL!\n", usb_id->idVendor, usb_id->idProduct);
        return DRV_ERR;
    }
    pthread_mutex_lock(&(dev_ofprint->usb_mutex));

    handle = libusb_open_device_with_vid_pid(dev_ofprint->ctx, usb_id->idVendor, usb_id->idProduct);
    if(NULL == handle)
    {
        DBG("libusb_open_device_with_vid_pid %04x:%04x : FAIL!\r\n", usb_id->idVendor, usb_id->idProduct);
        usb_handle->handle = NULL;
        pthread_mutex_unlock(&(dev_ofprint->usb_mutex));
        return DRV_ERR;
    }

    ret = libusb_claim_interface(handle, bInterfaceNumber);
    if(0 > ret)
    {
        ret = libusb_detach_kernel_driver(handle, bInterfaceNumber);
        if(0 > ret)
        {
            WD_ERR("libusb_detach_kernel_driver failed.\n");
            libusb_close(handle);
            goto END;
        }

        ret = libusb_claim_interface(handle, bInterfaceNumber);
        if(ret < 0)
        {
            libusb_close(handle);
            WD_ERR("libusb_claim_interface failed.\n");
            goto END;
        }
    }

    ret = 0;
    usb_handle->handle = (void *)handle;
END:
    pthread_mutex_unlock(&(dev_ofprint->usb_mutex));
    return ret;
}

int32 intf_usb_close(INTF_USB_HANDLE_STRUCT *usb_handle)
{
    if(NULL == usb_handle->handle)
    {
        //WD_ERR("usb device not open!\n");
        return DRV_ERR;
    }
    pthread_mutex_lock(&(dev_ofprint->usb_mutex));
    libusb_close((libusb_device_handle *)(usb_handle->handle));
    pthread_mutex_unlock(&(dev_ofprint->usb_mutex));

    usb_handle->handle = NULL;

    return DRV_OK;
}

int32 intf_usb_bulk_transfer(INTF_USB_HANDLE_STRUCT *usb_handle, INTF_USB_CMD_STRUCT *usb_cmd)
{
    libusb_device_handle *handle = NULL;
    uint8 endpoint_out = 0;
    uint8 endpoint_in = 0;
    int write_len = 0;
    int read_len = 0;
    int ret = -1;

    if((NULL == usb_cmd) || (NULL == usb_handle))
    {
        WD_ERR("the param is ERROR!\n");
        return DRV_ERR;
    }

    handle = (libusb_device_handle *)(usb_handle->handle);
    if(NULL == handle)
    {
        WD_ERR("usb device not open, error!\n");
        return DRV_ERR;
    }

    endpoint_out = usb_handle->endpoint_out;
    endpoint_in = usb_handle->endpoint_in;

    if((0 < usb_cmd->write_len) && (0 != endpoint_out) && (ENDPOINT_NUM_MAX >= endpoint_out))
    {
        pthread_mutex_lock(&(dev_ofprint->usb_bulk_mutex));
        ret = libusb_bulk_transfer(handle, endpoint_out, usb_cmd->buff, usb_cmd->write_len, &(write_len), usb_cmd->timeout);
        pthread_mutex_unlock(&(dev_ofprint->usb_bulk_mutex));
        if(0 != ret)
        {
            D_DBG("libusb_bulk_transfer write failed!!!\r\n");
            usb_cmd->write_len = 0;
            return DRV_ERR;
        }
        usb_cmd->write_len = write_len;
        ret = 0;
    }

    if((0 < usb_cmd->read_len) && (0x80 == (0x80 & endpoint_in)) \
        && (0 != (~0x80 & endpoint_in)) && (ENDPOINT_NUM_MAX >= (~0x80 & endpoint_in)))
    {
        pthread_mutex_lock(&(dev_ofprint->usb_bulk_mutex));
        ret = libusb_bulk_transfer(handle, endpoint_in, usb_cmd->buff, usb_cmd->read_len, &(read_len), usb_cmd->timeout);
        pthread_mutex_unlock(&(dev_ofprint->usb_bulk_mutex));
        if(0 != ret)
        {
            D_DBG("libusb_bulk_transfer read failed!!!\r\n");
            usb_cmd->read_len = 0;
            return DRV_ERR;
        }
        usb_cmd->read_len = read_len;
        ret = 0;
    }

    return ret;
}


int32 intf_usb_check_device_exist(INTF_USB_ID_STRUCT *usb_id)
{
    int rv = -2;
    int i = 0;
    int ret = -2;

    libusb_device **devs;
    libusb_device *dev;

    if(NULL == usb_id)
    {
        WD_ERR("the param is ERROR!\n");
        return -1;
    }
    pthread_mutex_lock(&(dev_ofprint->usb_mutex));

    rv = libusb_get_device_list(dev_ofprint->ctx, &devs);
    if (rv < 0)
    {
        goto LIBUSB_GET_DEVICE_LIST_FAILED;
    }

    i = 0;
    while ((dev = devs[i++]) != NULL)
    {
        struct libusb_device_descriptor desc;

        rv = libusb_get_device_descriptor(dev, &desc);
        if(rv < 0) {
            continue;
        }

        if((desc.idProduct == usb_id->idProduct) && (desc.idVendor == usb_id->idVendor))
        {
            ret = 0;
            goto LIBUSB_GET_DEVICE_LIST_SUCCESS;
        }
    }

LIBUSB_GET_DEVICE_LIST_SUCCESS:
    libusb_free_device_list(devs, 1);

LIBUSB_GET_DEVICE_LIST_FAILED:
    pthread_mutex_unlock(&(dev_ofprint->usb_mutex));
    return ret;
}


int32 intf_usb_get_device_path(INTF_USB_ID_STRUCT *usb_id, char *dev_port_path, int len)
{
    libusb_device **devs;
    libusb_device *dev;

    ssize_t cnt;
    int ret = -1;
    char port_path[20] = {0};

    int i = 0, j = 0;
    uint8_t path[8];

    WD_DBG("start.\r\n");

    memset(dev_port_path, 0x00, len);

    pthread_mutex_lock(&(dev_ofprint->usb_mutex));
    cnt = libusb_get_device_list(dev_ofprint->ctx, &devs);
    if (cnt < 0){
        goto LIBUSB_GET_DEVICE_LIST_FAILED;
    }

    while ((dev = devs[i++]) != NULL)
    {
        struct libusb_device_descriptor desc;
        int r = libusb_get_device_descriptor(dev, &desc);
        if (r < 0) {
            fprintf(stderr, "failed to get device descriptor");
            continue;
        }

        if((usb_id->idVendor == desc.idVendor) && (usb_id->idProduct == desc.idProduct))
        {
            WD_DBG("%04x:%04x (bus %d, device %d)\n",
                desc.idVendor, desc.idProduct,
                libusb_get_bus_number(dev), libusb_get_device_address(dev));

            r = libusb_get_port_numbers(dev, path, sizeof(path));
            if (r > 0) {
                WD_DBG(" path: %d", path[0]);
                snprintf(port_path, 20, "%d", path[0]);
                for (j = 1; j < r; j++)
                {
                    W_DBG(".%d", path[j]);
                    snprintf(port_path+strlen(port_path), 20-strlen(port_path), ".%d", path[j]);
                }
                W_DBG("\n");
            }
            else
            {
                goto LIBUSB_GET_DEVICE_LIST_SUCCESS;
            }

            snprintf(dev_port_path, len, "/sys/bus/usb/devices/%d-%s", libusb_get_bus_number(dev), port_path);
            WD_LOG("usb dev port path: %s\n", dev_port_path);
            ret = 0;

            WD_DBG("|--[Vid:0x%04x, Pid:0x%04x]-[Class:0x%02x, SubClass:0x%02x]-[bus:%d, device:%d, port:%d]-[cfg_desc_num:%d]\n",
                desc.idVendor, desc.idProduct, desc.bDeviceClass, desc.bDeviceSubClass,
                libusb_get_bus_number(dev), libusb_get_device_address(dev), libusb_get_port_number(dev), desc.bNumConfigurations);

            goto LIBUSB_GET_DEVICE_LIST_SUCCESS;
        }
    }

LIBUSB_GET_DEVICE_LIST_SUCCESS:
    libusb_free_device_list(devs, 1);

LIBUSB_GET_DEVICE_LIST_FAILED:
    pthread_mutex_unlock(&(dev_ofprint->usb_mutex));
    return ret;
}



